package com.vclee.fast.utils.sdk;

import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.aliyun.teaopenapi.models.Config;
import lombok.SneakyThrows;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.vclee.fast.utils.model.Bson;
import com.vclee.fast.utils.model.MediaInfo;
import com.vclee.fast.utils.model.Uploader;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;



@Component
public class AliUtil {

    private static final Logger logger = LoggerFactory.getLogger(AliUtil.class);

    private static String aliSmsKey;
    private static String aliSmsSecret;
    private static String aliOssKey;
    private static String aliOssSecret;
    private static String aliOssBucket;
    private static String aliOssEndpoint;
    private static  String aliOssProxy; // 代理地址。 默认 "https://" + aliOssBucket + "." + aliOssEndpoint




    @Value("${custom.ALI_SMS_KEY}")
    public void setSmsKey(String aliSmsKey) {
        AliUtil.aliSmsKey = aliSmsKey;
    }
    @Value("${custom.ALI_SMS_SECRET}")
    public void setSmsSecret(String aliSmsSecret) {
        AliUtil.aliSmsSecret = aliSmsSecret;
    }

    @Value("${custom.ALI_OSS_KEY}")
    public void setOssKey(String aliOssKey) {
        AliUtil.aliOssKey = aliOssKey;
    }
    @Value("${custom.ALI_OSS_SECRET}")
    public void setOssSecret(String aliOssSecret) {
        AliUtil.aliOssSecret = aliOssSecret;
    }
    @Value("${custom.ALI_OSS_BUCKET}")
    public void setOssBucketName(String aliOssBucket) {
        AliUtil.aliOssBucket = aliOssBucket;
    }
    @Value("${custom.ALI_OSS_ENDPOINT}")
    public void setOssEndpoint(String aliOssEndpoint) {
        AliUtil.aliOssEndpoint = aliOssEndpoint;
    }
    @Value("${custom.ALI_OSS_PROXY}")
    public void setOssProxyHost(String aliOssProxy) {
        AliUtil.aliOssProxy = aliOssProxy;
    }
    private static final Config config =  new Config();
    @PostConstruct
    public void init(){
        config.setAccessKeyId(aliSmsKey);
        config.setAccessKeySecret(aliSmsSecret);
        config.endpoint = "dysmsapi.aliyuncs.com";
    }

    /**
     * @param phone 手机号
     * @param tplCode 模板编号
     * @param tplSign 短信签名
     * @param tplParams  短信模板变量对应的实际值,JSON格式(eg:模板值为code则templateParams使用put方法新增一个key为code和对应的值)
     * @return 短信发送结果
     */
    @SneakyThrows
    public static SendSmsResponse sendSms(String phone, String tplCode,String tplSign, Bson tplParams){
        SendSmsRequest request = new SendSmsRequest();
        request.setPhoneNumbers(phone);
        request.setTemplateCode(tplCode);
        request.setSignName(tplSign); // signName为申请通过的签名信息,eg:常春藤
        request.setTemplateParam(tplParams.toString());// JSON字符串 eg："{\"name\":\"张三\",\"number\":\"1390000****\"}"
        Client client = new Client(config);
        SendSmsResponse response = client.sendSms(request);
        logger.info("阿里SDK发送短信结果:{},参数信息,phone:{},code:{},template:{}," +
                        "sign:{},smsKey:{},smsSecret:{}", response.body.getMessage(), phone,
                tplCode, tplParams, tplSign, config.accessKeyId, config.accessKeySecret);
        return response;
    }

    public static Uploader getOssTicket(String fileName) {
        String key = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM")) + "/" + fileName;
        OSSClient client = new OSSClient(aliOssEndpoint, aliOssKey, aliOssSecret);
        long expireTime = 600;
        long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
        Date expiration = new Date(expireEndTime);

        PolicyConditions policyConditions = new PolicyConditions();
        policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
        policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, key);

        String postPolicy = client.generatePostPolicy(expiration, policyConditions);
        byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
        String encodedPolicy = BinaryUtil.toBase64String(binaryData);
        String postSignature = client.calculatePostSignature(postPolicy);
        client.shutdown();

        Bson jasonCallback = new Bson();
        jasonCallback.put("callbackBody", "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
        jasonCallback.put("callbackBodyType", "application/json");
        String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
        return new Uploader() {{
            setOSSAccessKeyId(aliOssKey);
            setPolicy(encodedPolicy);
            setSignature(postSignature);
            setKey(key);
            setSuccess_action_status("200");
            setExpire(String.valueOf(expireEndTime / 1000));
            setHost(aliOssProxy);
            setCallback(base64CallbackBody);
        }};
    }

    @SneakyThrows
    public static String uploadFile(String fileName, InputStream stream) {
        OSSClient client = new OSSClient(aliOssEndpoint, aliOssKey, aliOssSecret);
        String path =  LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"))+"/"+fileName;
        client.putObject(aliOssBucket, path, stream);
        return aliOssProxy + "/" + path;
    }


    /**
     * 验证上传回调的Request
     *
     * @param request http请求
     */
    public static MediaInfo getOssCallBack(HttpServletRequest request) throws NumberFormatException, IOException {
        String ossCallbackBody = GetPostBody(request.getInputStream(), Integer.parseInt(request.getHeader("content-length")));
        String atomizationInput = request.getHeader("Authorization");
        String pubKeyInput = request.getHeader("x-oss-pub-key-url");
        byte[] authorization = BinaryUtil.fromBase64String(atomizationInput);
        byte[] pubKey = BinaryUtil.fromBase64String(pubKeyInput);

        String pubKeyAddr = new String(pubKey);
        if (!pubKeyAddr.startsWith("http://gosspublic.alicdn.com/") && !pubKeyAddr.startsWith("https://gosspublic.alicdn.com/")) {
            logger.warn("pub key addr must be oss address");
            return null;
        }
        String retString = executeGet(pubKeyAddr);
        retString = retString.replace("-----BEGIN PUBLIC KEY-----", "");
        retString = retString.replace("-----END PUBLIC KEY-----", "");
        String queryString = request.getQueryString();
        String uri = request.getRequestURI();
        String authStr = URLDecoder.decode(uri, "UTF-8");
        if (queryString != null && !queryString.isEmpty()) authStr += "?" + queryString;
        authStr += "\n" + ossCallbackBody;
        return doCheck(authStr, authorization, retString) ? getJsonStrByQueryUrl(ossCallbackBody) : null;
    }


    /**
     * @param paramStr url参数格式 eg: "a=a1&b=b1&c=c1"
     * @return MediaInfo
     */
    private static MediaInfo getJsonStrByQueryUrl(String paramStr) {
        String[] params = paramStr.replaceAll("\"", "").split("&");
        Map<String, String> obj = new HashMap<>();
        for (String s : params) {
            String[] param = s.split("=");
            if (param.length >= 2) {
                String key = param[0];
                String value = param[1];
                try {
                    obj.put(key, value);
                } catch (Exception ignored) {
                    logger.error("getJsonStrByQueryUrl error: " + key + "=" + value);
                }
            }
        }
        return Bson.toClazz(obj, MediaInfo.class);
    }

    /**
     * 获取Post消息体
     */
    private static String GetPostBody(InputStream is, int contentLen) {
        if (contentLen > 0) {
            int readLen = 0;
            int readLengthThisTime = 0;
            byte[] message = new byte[contentLen];
            try {
                while (readLen != contentLen) {
                    readLengthThisTime = is.read(message, readLen, contentLen - readLen);
                    if (readLengthThisTime == -1) {// Should not happen.
                        break;
                    }
                    readLen += readLengthThisTime;
                }
                return new String(message);
            } catch (IOException ignored) {
            }
        }
        return "";
    }

    /**
     * 获取public key
     *
     * @param url 获取公钥的地址
     */
    private static String executeGet(String url) {
        BufferedReader in = null;
        String content = null;
        try {
            // 定义HttpClient并实例化HTTP方法
            DefaultHttpClient client = new DefaultHttpClient();
            HttpGet request = new HttpGet();
            request.setURI(new URI(url));
            HttpResponse response = client.execute(request);
            in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            StringBuilder sb = new StringBuilder("");
            String line = "";
            String NL = System.getProperty("line.separator");
            while ((line = in.readLine()) != null) {
                sb.append(line).append(NL);
            }
            in.close();
            content = sb.toString();
        } catch (Exception ignored) {
        } finally {
            if (in != null) {
                try {
                    in.close();// 最后要关闭BufferedReader
                } catch (Exception e) {
                    logger.error("关闭流异常:{}", e.getMessage(), e);
                }
            }
        }
        return content;
    }


    /**
     * 验证RSA
     *
     * @param content   内容
     * @param sign      签名
     * @param publicKey 公钥
     */
    private static boolean doCheck(String content, byte[] sign, String publicKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] encodedKey = BinaryUtil.fromBase64String(publicKey);
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
            Signature signature = Signature.getInstance("MD5withRSA");
            signature.initVerify(pubKey);
            signature.update(content.getBytes());
            return signature.verify(sign);
        } catch (Exception e) {
            logger.error("rsa验证失败");
        }
        return false;
    }

}
